package compile

import (
	"fmt"
	"strconv"
	"strings"

	"gitee.com/caivega/easm/core"
	"gitee.com/caivega/easm/util"
)

const (
	ASM_EXT      string = ".asm"
	ASM_MAIN     string = "<main>"
	SECTION_DATA string = "data"
	SECTION_CODE string = "text"
	SECTION_CALL string = "call"
)

type Line struct {
	X, Y    int
	code    string
	comment string
}

type Label struct {
	n     string
	t     core.LabelType
	attrs map[string]string
	is    bool

	b *Block
	m *Module
	s *Section
	f *Function
	c *Call
	d *Data
	p *Parameter
	i *BlockItem
}

func (l *Label) IsLabelType(t core.LabelType) bool {
	return l.t == t
}

func (l *Label) IsData() bool {
	return l.t == core.LABEL_CONST || l.t == core.LABEL_VAR || l.t == core.LABEL_SLOT || l.t == core.LABEL_LOCAL
}

func (l *Label) IsBlock(name string) bool {
	return l.t == core.LABEL_BLOCK && l.i != nil && l.i.name == name
}

type Block struct {
	parent *Block
	label  *Label

	nameMap  map[string]*Label
	nameList []string
}

func CreateBlock() *Block {
	return &Block{nameMap: make(map[string]*Label)}
}

func (b *Block) IsType(t core.LabelType) bool {
	return b.label != nil && b.label.IsLabelType(t)
}

func (b *Block) IsData() bool {
	return b.label != nil && b.label.IsData()
}

func (b *Block) Put(l *Label) {
	_, ok := b.nameMap[l.n]
	if ok {
		panic("exists label: " + l.n)
	}
	ok = isName(l.n)
	if !ok && !l.is {
		panic("invalid name: " + l.n)
	}
	b.nameMap[l.n] = l
	b.nameList = append(b.nameList, l.n)
}

type Module struct {
	filePath string

	labelMap map[string]*Label
}

type Section struct {
	name      string
	main      *Function
	slotCount int // slots
}

type Call struct {
	name   string
	params []*Label

	// update jump address
	cb     *core.Buffer
	offset int
}

type Parameter struct {
	name   string
	params []*Label
}

type Function struct {
	params      []*Label
	returns     []*Label
	stackOffset int // about localCount

	calls      []*Call
	postion    int  // the start position of the function in the code
	localCount int  // parameter(s) + local(s)
	hasReturn  bool // has return in function
}

func (f *Function) Offset() int {
	return f.localCount + f.stackOffset
}

type Data struct {
	t     core.DataType
	attrs map[string]string

	slotIndex     int // index for slot
	varIndex      int // index for var
	localIndex    int // local index(used in stack) in function
	callDataIndex int // index in call data(0,1,2,...,n)
}

type OperatorItem struct {
	t     core.OperatorType
	value *Label

	params []*OperatorItem
}

type BlockItem struct {
	name  string
	t     core.BlockItemType
	value string
}

type Pool struct {
	stringList []string
	stringMap  map[string]int
}

func NewPool() *Pool {
	return &Pool{stringList: make([]string, 0), stringMap: make(map[string]int)}
}

func (p *Pool) putString(v string) string {
	index, ok := p.stringMap[v]
	if !ok {
		index = len(p.stringList)
	}
	k := fmt.Sprintf("\"${%d}\"", index)
	p.stringList = append(p.stringList, v)
	p.stringMap[v] = index
	return k
}

func (p *Pool) getString(k string) (string, bool) {
	prefixString := "\"${"
	suffixString := "}\""
	if strings.HasPrefix(k, prefixString) && strings.HasSuffix(k, suffixString) {
		numString := strings.TrimSuffix(strings.TrimPrefix(k, prefixString), suffixString)
		num, err := strconv.ParseInt(numString, 10, 64)
		if err != nil {
			panic(err)
		}
		return p.stringList[num], true
	}
	return "", false
}

type Memory struct {
	offset int // size: 0x20(32 bytes)
}

type Current struct {
	blocks      []*Block
	pool        *Pool
	config      *core.Config
	memory      *Memory
	commentMap  map[string][]string
	updateFuncs []*Function
}

func Create(b *Block, c *core.Config) *Current {
	return &Current{[]*Block{b}, NewPool(), c, &Memory{offset: 0}, make(map[string][]string), make([]*Function, 0)}
}

func (c *Current) Reset(b *Block) {
	root := c.blocks[0]
	if b == nil {
		c.blocks = []*Block{root}
	} else {
		blocks := make([]*Block, 0)
		p := b
		for p != nil {
			blocks = append([]*Block{p}, blocks...)
			p = p.parent
		}
		if blocks[0] != root {
			panic("error root")
		}
		c.blocks = blocks
	}
}

func (c *Current) Push(b *Block) {
	b.parent = c.Top()
	c.blocks = append(c.blocks, b)
}

func (c *Current) Empty() bool {
	return (len(c.blocks) == 0)
}

func (c *Current) Pop() *Block {
	if !c.Empty() {
		ll := len(c.blocks)
		item := c.blocks[ll-1]
		c.blocks = c.blocks[:ll-1]
		return item
	}
	return nil
}

func (c *Current) Root() *Block {
	if !c.Empty() {
		return c.blocks[0]
	}
	return nil
}

func (c *Current) Top() *Block {
	if !c.Empty() {
		return c.blocks[len(c.blocks)-1]
	}
	return nil
}

func (c *Current) Chain(t core.LabelType) *Block { // find last the label in current chain
	for i := len(c.blocks) - 1; i >= 0; i-- {
		b := c.blocks[i]
		if b.label != nil && b.IsType(t) {
			return b
		}
	}
	return nil
}

func (c *Current) GetComment(key string) ([]byte, bool) {
	returned, ok := c.commentMap[key]
	if ok && len(returned) == 1 {
		data, err := util.HexToBytes(returned[0])
		if err != nil {
			panic(err)
		}
		return data, true
	}
	return nil, false
}

func (c *Current) GetComments(key string) ([]string, bool) {
	returned, ok := c.commentMap[key]
	if ok {
		list := make([]string, 0)
		for i := 0; i < len(returned); i++ {
			list = append(list, returned[i])
		}
		return list, true
	}
	return nil, false
}

func (c *Current) IsDebug() bool {
	return c.config.Debug
}

func (c *Current) SetDebug(debug bool) {
	c.config.Debug = debug
}
